﻿#include "stdafx.h"
#include "sbcs.h"
#if _SB_UNIX_
#include "sys/time.h"
#endif

//////////////////////////////////////////////////////////////////////////
// 틱

#if _SB_WINDOWS_ && _SB_WINDOWS_DESKTOP_
typedef MMRESULT(WINAPI* type_timeBeginPeriod)(__in UINT uPeriod);
typedef MMRESULT(WINAPI* type_timeEndPeriod)(__in UINT uPeriod);

static int _k_tickres_value;

static void _k_disp_tickres(kpointer ptr)
{
	HMODULE dll = LoadLibrary(L"winmm");

	if (dll)
	{
		type_timeEndPeriod func = (type_timeEndPeriod)GetProcAddress(dll, "timeEndPeriod");

		if (func)
			func(_k_tickres_value);

		FreeLibrary(dll);
	}
}
#endif

// 해상도 업
kcham k_tickres(int n)
{
#if !_SB_WINDOWS_ || !_SB_WINDOWS_DESKTOP_
	return TRUE;
#else
	kcham ret;
	HMODULE dll;

	k_init();

	ret = FALSE;

	dll = LoadLibrary(L"winmm");

	if (dll)
	{
		type_timeBeginPeriod func = (type_timeBeginPeriod)GetProcAddress(dll, "timeBeginPeriod");

		if (func)
			ret = func(n) == TIMERR_NOERROR;

		FreeLibrary(dll);

		if (ret)
		{
			// 종료때 해제
			_k_tickres_value = n;
			k_atexit_p(_k_disp_tickres, NULL);
		}
	}

	return ret;
#endif
}

// 틱
static klong k_tick(void)
{
#if _SB_WINDOWS_
	LARGE_INTEGER ll;
	QueryPerformanceCounter(&ll);
	return ll.QuadPart;
#elif _SB_BSD_
	klong n;
	struct timespec tp;

	if (clock_gettime(CLOCK_REALTIME, &tp) == 0)
		n = ((klong)tp.tv_sec * 1000) + ((klong)tp.tv_nsec / 1000000);
	else
	{
		struct timeval tv;
		gettimeofday(&tv, 0);
		n = ((klong)tv.tv_sec * 1000) + ((klong)tv.tv_usec / 1000);
	}

	return n;
#else
	klong n;
	struct timeval tv;
	gettimeofday(&tv, 0);
	n = (klong)tv.tv_sec * 1000 + (klong)tv.tv_usec / 1000;
	return n;
#endif
}

// 틱값
static double k_pertick(void)
{
#if _SB_WINDOWS_
	static kcham s_init = FALSE;
	static double s_value = 0.001;

	if (!s_init)
	{
		LARGE_INTEGER ll;

		if (QueryPerformanceFrequency(&ll))
			s_value = 1.0 / (double)ll.QuadPart;

		s_init = TRUE;
	}

	return s_value;
#else
	return 0.001;
#endif
}

// 틱
double k_curtick()
{
	double t = (double)k_tick();
	double p = k_pertick();
	return t * p;
}

// 틱 카운트
kuint k_gettick()
{
#if _SB_WINDOWS_
	klong l = (klong)(k_curtick() * 1000.0);
	return (kuint)(l & 0xFFFFFFFF);
#else
	return (kuint)(k_tick() & 0xFFFFFFFF);
#endif
}

// 64비트 틱 카운트
kulong k_gettick_long()
{
#if _SB_WINDOWS_
	klong l = (klong)(k_curtick() * 1000.0);
	return l;
#else
	return k_tick();
#endif
}

// 주기
kuint k_cycles()
{
#if _SB_WINDOWS_
	LARGE_INTEGER l;
	QueryPerformanceCounter(&l);
	return l.LowPart;
#else
	struct timeval tv;
	gettimeofday(&tv, NULL);
	return (kuint)((((kulong)tv.tv_sec) * K_CONST_ULONG(1000000)) + (((kulong)tv.tv_usec)));
#endif
}


//////////////////////////////////////////////////////////////////////////
// 슬립

/// 밀리초 슬립.
/// @date 2013-12-22
/// @param milliseconds 밀리초.
void k_sleep(kuint milliseconds)
{
#if _SB_WINDOWS_
#if _SB_WINDOWS_DESKTOP_
	Sleep(milliseconds);
#else
	HANDLE h = CreateEventEx(NULL, NULL, CREATE_EVENT_MANUAL_RESET, EVENT_ALL_ACCESS);
	WaitForSingleObjectEx(h, milliseconds, FALSE);
	CloseHandle(h);
#endif
#else
	uint s = milliseconds / 1000;
	uint u = (milliseconds % 1000) * 1000;
	sleep(s);
	usleep(u);
#endif
}

/// 마이크로초 슬립.
/// @date 2013-12-22
/// @param microseconds 마이크로초.
void k_usleep(kuint microseconds)
{
#if _SB_WINDOWS_
	k_sleep(microseconds / 1000);
#else
	usleep(microseconds);
#endif
}

/// 초 슬립.
/// @date 2013-12-22
/// @param seconds 초.
void k_ssleep(kuint seconds)
{
	k_sleep(seconds * 1000);
}

/// 마이크로 슬립, 정밀 시계를 이용하며 스레드 콘텍스트가 일반 슬립보다 제한된다.
/// @date 2013-12-22
/// @param microseconds 마이크로초.
/// ### remarks KSH, 2013-12-22.
void k_msleep(kulong microseconds)
{
#if _SB_WINDOWS_
	double dms = (double)microseconds;
	LARGE_INTEGER t1, t2, freq;

	if (!QueryPerformanceFrequency(&freq))
		k_usleep((kuint)microseconds);
	else
	{
		QueryPerformanceCounter(&t1);

		do
		{
#if _SB_WINDOWS_DESKTOP_
			SwitchToThread();	// XP 이상
#endif
			QueryPerformanceCounter(&t2);
		} while (((double)(t2.QuadPart - t1.QuadPart) / freq.QuadPart * 1000000) < dms);
	}
#elif _SB_UNIX_
	struct timespec ts;
	ts.tv_sec = microseconds / 1000000;
	ts.tv_nsec = (microseconds % 1000000) * 1000;

	while (nanosleep(&ts, &ts))
	{
		if (errno != EINTR)
			break;
	}
#else
	usleep(microseconds);
#endif
}


//////////////////////////////////////////////////////////////////////////
// 타이머
typedef struct kRealTimer
{
	kTimer				base;

	kcham				stop;
	kint				past;
	double              cut;

	kvlong				basetime;
	kvlong				stoptime;
	kvlong				lasttime;
	kvlong				curtime;
	kvlong				frmtime;

	kuint				count;

	double				tick;
	kvlong				frame;

	kcham				manual;	
	double				fps_abs;
	kint				fps_frame;
} kRealTimer;

// 만들기
kTimer* k_timer_new(void)
{
	kRealTimer* self;
#if _SB_WINDOWS_
	LARGE_INTEGER ll;
#endif

	self = k_new_0(kRealTimer);

#if _SB_WINDOWS_
	if (!QueryPerformanceFrequency(&ll))
	{
		self->frame.q = 1000;
		self->tick = 0.001;
	}
	else
	{
		self->frame.q = ll.QuadPart;
		self->tick = 1.0 / (double)ll.QuadPart;
	}
#else
	self->frame.q = 1000;
	self->tick = 0.001;
#endif

	self->curtime.q = k_tick();
	self->basetime.q = self->curtime.q;
	self->lasttime.q = self->curtime.q;
	self->count = self->curtime.dw.l;

	self->cut = 9999999.0;  //10.0;

	return (kTimer*)self;
}

// 제거
void k_timer_delete(kTimer* self)
{
	k_free(self);
}

// 리셋
void k_timer_reset(kTimer* self)
{
	kRealTimer* impl = (kRealTimer*)self;

	impl->curtime.q = (impl->stoptime.q != 0) ? impl->stoptime.q : (kulong)k_tick();
	impl->basetime.q = impl->curtime.q;
	impl->lasttime.q = impl->curtime.q;
	impl->stoptime.q = 0;
	impl->count = impl->curtime.dw.l;

	impl->stop = FALSE;
}

// 시작
void k_timer_start(kTimer* self)
{
	kRealTimer* impl = (kRealTimer*)self;

	impl->curtime.q = (impl->stoptime.q != 0) ? impl->stoptime.q : (kulong)k_tick();

	if (impl->stop)
		impl->basetime.q += impl->curtime.q - impl->stoptime.q;

	impl->lasttime.q = impl->curtime.q;
	impl->stoptime.q = 0;
	impl->count = impl->curtime.dw.l;

	impl->stop = FALSE;
}

// 스탑
void k_timer_stop(kTimer* self)
{
	kRealTimer* impl = (kRealTimer*)self;

	impl->curtime.q = (impl->stoptime.q != 0) ? impl->stoptime.q : (kulong)k_tick();
	impl->lasttime.q = impl->curtime.q;
	impl->stoptime.q = impl->curtime.q;
	impl->count = impl->curtime.dw.l;

	impl->stop = TRUE;
}

// 업데이트
kcham k_timer_update(kTimer* self)
{
	kRealTimer* impl = (kRealTimer*)self;
	kcham ret;

	impl->curtime.q = (impl->stoptime.q != 0) ? impl->stoptime.q : (kulong)k_tick();
	impl->base.abstime = (double)impl->curtime.q * impl->tick;
	impl->base.runtime = (double)(impl->curtime.q - impl->basetime.q) * impl->tick;

	if (!impl->manual)
		impl->base.fps = (double)impl->frame.dw.l / (double)(impl->curtime.dw.l - impl->count);
	else
	{
		impl->fps_frame++;

		if ((impl->base.abstime - impl->fps_abs) >= 1.0)
		{
			impl->base.fps = (float)impl->fps_frame;
			impl->fps_abs = impl->base.abstime;
			impl->fps_frame = 0;
		}
	}

	impl->count = impl->curtime.dw.l;

	if (impl->base.fps < impl->cut)
	{
		impl->base.advance = (double)(impl->curtime.q - impl->lasttime.q) * impl->tick;
		ret = TRUE;
	}
	else
	{
		impl->base.advance = impl->cut * 0.001;
		ret = FALSE;
	}

	impl->lasttime.q = impl->curtime.q;
	impl->past = (int)(impl->base.advance * 1000.0);
	return ret;
}

// 시간
double k_timer_get_abs(kTimer* self)
{
	return self->abstime;
}

// 실행
double k_timer_get_run(kTimer* self)
{
	return  self->runtime;
}

// fps
double k_timer_get_fps(kTimer* self)
{
	return self->fps;
}

// 경과
double k_timer_get_adv(kTimer* self)
{
	return self->advance;
}

// 컷
double k_timer_get_cut(kTimer* self)
{
	kRealTimer* impl = (kRealTimer*)self;
	return impl->cut;
}

// 컷
void k_timer_set_cut(kTimer* self, double cut)
{
	kRealTimer* impl = (kRealTimer*)self;
	impl->cut = cut;
}

// 매뉴얼 타입 FPS 측정
void k_timer_set_manual(kTimer* self, kcham value)
{
	kRealTimer* impl = (kRealTimer*)self;
	impl->manual = value;
}

